package cn.com.greatwall.flowable.service.impl;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.flowable.bpmn.constants.BpmnXMLConstants;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.EndEvent;
import org.flowable.bpmn.model.ExtensionElement;
import org.flowable.bpmn.model.FlowElement;
import org.flowable.bpmn.model.FlowNode;
import org.flowable.bpmn.model.Process;
import org.flowable.bpmn.model.UserTask;
import org.flowable.bpmn.model.ValuedDataObject;
import org.flowable.common.engine.api.FlowableObjectNotFoundException;
import org.flowable.common.engine.impl.identity.Authentication;
import org.flowable.editor.language.json.converter.util.CollectionUtils;
import org.flowable.engine.FormService;
import org.flowable.engine.HistoryService;
import org.flowable.engine.IdentityService;
import org.flowable.engine.ManagementService;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.engine.repository.ProcessDefinition;
import org.flowable.engine.runtime.ActivityInstance;
import org.flowable.engine.runtime.Execution;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.identitylink.api.IdentityLinkType;
import org.flowable.identitylink.api.history.HistoricIdentityLink;
import org.flowable.idm.api.Group;
import org.flowable.idm.api.User;
import org.flowable.task.api.Task;
import org.flowable.task.api.TaskInfo;
import org.flowable.task.api.TaskQuery;
import org.flowable.task.api.history.HistoricTaskInstance;
import org.flowable.task.api.history.HistoricTaskInstanceQuery;
import org.flowable.task.service.impl.persistence.entity.TaskEntity;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import cn.com.greatwall.common.utils.DateUtil;
import cn.com.greatwall.common.utils.PageUtil;
import cn.com.greatwall.common.utils.SecurityUtils;
import cn.com.greatwall.flowable.common.CommentTypeEnum;
import cn.com.greatwall.flowable.common.cmd.BackUserTaskCmd;
import cn.com.greatwall.flowable.common.constant.FlowableConstant;
import cn.com.greatwall.flowable.common.exception.FlowableNoPermissionException;
import cn.com.greatwall.flowable.service.PermissionService;
import cn.com.greatwall.flowable.service.TaskService;
import cn.com.greatwall.flowable.service.dto.FlowNodeDto;
import cn.com.greatwall.flowable.service.dto.TaskDto;
import cn.com.greatwall.flowable.service.dto.TaskInfoDto;
import cn.com.greatwall.flowable.service.dto.criteria.TaskQueryCriteria;
import cn.com.greatwall.flowable.service.mapstruct.TaskMapper;
import cn.com.greatwall.flowable.util.FlowableUtils;
import lombok.RequiredArgsConstructor;

/**
 * @Author herw
 * @Time 2020-11-24 15:42:52
 * @Version 1.0
 * @Description: TODO(用一句话描述该文件做什么)
 */
@Service
@RequiredArgsConstructor
public class TaskServiceImpl implements TaskService {
    private final PermissionService permissionService;
    private final RepositoryService repositoryService;
    private final FormService formService;
    private final HistoryService historyService;
    private final IdentityService identityService;
    private final RuntimeService runtimeService;
    private final ManagementService managementService;
    private final org.flowable.engine.TaskService taskService;
    private final TaskMapper taskMapper;

    private HistoricTaskInstanceQuery query(TaskQueryCriteria criteria) {
        HistoricTaskInstanceQuery query = historyService.createHistoricTaskInstanceQuery();
        if (ObjectUtils.isNotEmpty(criteria.getTaskId())) {
            query.taskId(criteria.getTaskId());
        }
        if (ObjectUtils.isNotEmpty(criteria.getProcessInstanceId())) {
            query.processInstanceId(criteria.getProcessInstanceId());
        }
        if (ObjectUtils.isNotEmpty(criteria.getProcessInstanceBusinessKey())) {
            query.processInstanceBusinessKeyLike("%" + criteria.getProcessInstanceBusinessKey() + "%");
        }
        if (ObjectUtils.isNotEmpty(criteria.getProcessDefinitionKey())) {
            query.processDefinitionKeyLike("%" + criteria.getProcessDefinitionKey() + "%");
        }
        if (ObjectUtils.isNotEmpty(criteria.getProcessDefinitionId())) {
            query.processDefinitionId(criteria.getProcessDefinitionId());
        }
        if (ObjectUtils.isNotEmpty(criteria.getProcessDefinitionName())) {
            query.processDefinitionNameLike("%" + criteria.getProcessDefinitionName() + "%");
        }
        if (ObjectUtils.isNotEmpty(criteria.getExecutionId())) {
            query.executionId(criteria.getExecutionId());
        }
        if (ObjectUtils.isNotEmpty(criteria.getTaskName())) {
            query.taskNameLike("%" + criteria.getTaskName() + "%");
        }
        if (ObjectUtils.isNotEmpty(criteria.getTaskDescription())) {
            query.taskDescriptionLike("%" + criteria.getTaskDescription() + "%");
        }
        if (ObjectUtils.isNotEmpty(criteria.getTaskDefinitionKey())) {
            query.taskDefinitionKeyLike("%" + criteria.getTaskDefinitionKey() + "%");
        }
        if (ObjectUtils.isNotEmpty(criteria.getTaskAssignee())) {
            query.taskAssignee(criteria.getTaskAssignee());
        }
        if (ObjectUtils.isNotEmpty(criteria.getTaskOwner())) {
            query.taskOwner(criteria.getTaskOwner());
        }
        if (ObjectUtils.isNotEmpty(criteria.getTaskInvolvedUser())) {
            query.taskInvolvedUser(criteria.getTaskInvolvedUser());
        }
        if (ObjectUtils.isNotEmpty(criteria.getTaskPriority())) {
            query.taskPriority(criteria.getTaskPriority());
        }
        if (ObjectUtils.isNotEmpty(criteria.getFinished())) {
            if (criteria.getFinished()) {
                query.finished();
            } else {
                query.unfinished();
            }
        }
        if (ObjectUtils.isNotEmpty(criteria.getProcessFinished())) {
            if (criteria.getProcessFinished()) {
                query.processFinished();
            } else {
                query.processUnfinished();
            }
        }
        if (ObjectUtils.isNotEmpty(criteria.getParentTaskId())) {
            query.taskParentTaskId(criteria.getParentTaskId());
        }
        if (ObjectUtils.isNotEmpty(criteria.getTenantId())) {
            query.taskTenantId(criteria.getTenantId());
        }
        if (ObjectUtils.isNotEmpty(criteria.getTaskCandidateUser())) {
            query.taskCandidateUser(criteria.getTaskCandidateUser());
        }
        if (ObjectUtils.isNotEmpty(criteria.getTaskCandidateGroup())) {
            query.taskCandidateGroup(criteria.getTaskCandidateGroup());
        }
        if (ObjectUtils.isNotEmpty(criteria.getTaskCandidateGroups())) {
            query.taskCandidateGroupIn(Arrays.asList(criteria.getTaskCandidateGroups().split(",")));
        }
        if (ObjectUtils.isNotEmpty(criteria.getDueDateAfter())) {
            query.taskDueAfter(DateUtil.strToDate(criteria.getDueDateAfter(), null));
        }
        if (ObjectUtils.isNotEmpty(criteria.getDueDateBefore())) {
            query.taskDueBefore(DateUtil.strToDate(criteria.getDueDateBefore(), null));
        }
        if (ObjectUtils.isNotEmpty(criteria.getTaskCreatedBefore())) {
            query.taskCreatedBefore(DateUtil.strToDateTime(criteria.getTaskCreatedBefore()));
        }
        if (ObjectUtils.isNotEmpty(criteria.getTaskCreatedBefore())) {
            query.taskCreatedAfter(DateUtil.strToDateTime(criteria.getTaskCreatedBefore()));
        }
        if (ObjectUtils.isNotEmpty(criteria.getTaskCompletedBefore())) {
            query.taskCompletedBefore(DateUtil.strToDateTime(criteria.getTaskCompletedBefore()));
        }
        if (ObjectUtils.isNotEmpty(criteria.getTaskCompletedAfter())) {
            query.taskCompletedAfter(DateUtil.strToDateTime(criteria.getTaskCompletedAfter()));
        }

        return query;
    }

    @Override
    public Map<String, Object> queryAll(TaskQueryCriteria criteria, Pageable pageable) {
        HistoricTaskInstanceQuery query = query(criteria);
        List list = query.listPage((int) pageable.getOffset(), pageable.getPageSize());

        return PageUtil.toPage(taskMapper.toDto(list), query.count());
    }

    @Override
    public TaskInfoDto findTaskInfoById(String taskId) {
        String userId = SecurityUtils.getCurrentUserId().toString();
        HistoricTaskInstance historicTaskInstance = permissionService.validateReadPermissionOnTaskInstance(taskId,
                userId, true, true);
        TaskInfoDto dto = new TaskInfoDto();
        ProcessDefinition processDefinition = null;
        String formKey = null;
        Object renderedTaskForm = null;
        HistoricTaskInstance parentTask = null;
        if (StringUtils.isNotEmpty(historicTaskInstance.getProcessDefinitionId())) {
            processDefinition = repositoryService.getProcessDefinition(historicTaskInstance.getProcessDefinitionId());
            formKey = formService.getTaskFormKey(processDefinition.getId(),
                    historicTaskInstance.getTaskDefinitionKey());
            if (historicTaskInstance.getEndTime() == null && formKey != null && formKey.length() > 0) {
                renderedTaskForm = formService.getRenderedStartForm(taskId);
            }
        }
        if (StringUtils.isNotEmpty(historicTaskInstance.getParentTaskId())) {
            parentTask = historyService.createHistoricTaskInstanceQuery().taskId(historicTaskInstance.getParentTaskId())
                    .singleResult();
        }
        dto = new TaskInfoDto(historicTaskInstance, processDefinition, parentTask, null);
        dto.setFormKey(formKey);
        dto.setRenderedTaskForm(renderedTaskForm);
        //
        fillPermissionInformation(dto, historicTaskInstance, userId);
        // Populate the people
        populateAssignee(historicTaskInstance, dto);

        dto.setInvolvedPeople(getInvolvedUsers(taskId));

        Task task = null;
        if (historicTaskInstance.getEndTime() == null) {
            task = taskService.createTaskQuery().taskId(taskId).singleResult();

            dto.setSuspended(task.isSuspended());
            dto.setDelegationState(task.getDelegationState());
        }

        dto.setOwnerName(getUserName(historicTaskInstance.getOwner()));
        dto.setAssigneeName(getUserName(historicTaskInstance.getAssignee()));

        return dto;
    }

    @Override
    public Map<String, Object> queryTodo(TaskQueryCriteria criteria, Pageable pageable) {
        TaskQuery query = taskService.createTaskQuery();
        if (ObjectUtils.isNotEmpty(criteria.getProcessInstanceId())) {
            query.processInstanceId(criteria.getProcessInstanceId());
        }
        if (ObjectUtils.isNotEmpty(criteria.getTaskName())) {
            query.taskNameLike(criteria.getTaskName());
        }
        if (ObjectUtils.isNotEmpty(criteria.getProcessInstanceBusinessKey())) {
            query.processInstanceBusinessKeyLike("%" + criteria.getProcessInstanceBusinessKey() + "%");
        }
        if (ObjectUtils.isNotEmpty(criteria.getProcessDefinitionKey())) {
            query.processDefinitionKeyLike("%" + criteria.getProcessDefinitionKey() + "%");
        }
        if (ObjectUtils.isNotEmpty(criteria.getProcessDefinitionId())) {
            query.processDefinitionId(criteria.getProcessDefinitionId());
        }
        if (ObjectUtils.isNotEmpty(criteria.getProcessDefinitionName())) {
            query.processDefinitionNameLike("%" + criteria.getProcessDefinitionName() + "%");
        }
        if (ObjectUtils.isNotEmpty(criteria.getDueDateAfter())) {
            query.taskDueAfter(DateUtil.strToDate(criteria.getDueDateAfter(), null));
        }
        if (ObjectUtils.isNotEmpty(criteria.getDueDateBefore())) {
            query.taskDueBefore(DateUtil.strToDate(criteria.getDueDateBefore(), null));
        }
        if (ObjectUtils.isNotEmpty(criteria.getTaskCreatedBefore())) {
            query.taskCreatedBefore(DateUtil.strToDateTime(criteria.getTaskCreatedBefore()));
        }
        if (ObjectUtils.isNotEmpty(criteria.getTaskCreatedAfter())) {
            query.taskCreatedAfter(DateUtil.strToDateTime(criteria.getTaskCreatedAfter()));
        }
        if (ObjectUtils.isNotEmpty(criteria.getTenantId())) {
            query.taskTenantId(criteria.getTenantId());
        }
        if (ObjectUtils.isNotEmpty(criteria.getSuspended())) {
            if (criteria.getSuspended()) {
                query.suspended();
            } else {
                query.active();
            }
        }

        String userId = SecurityUtils.getCurrentUserId().toString();
        query.or().taskCandidateOrAssigned(userId).taskOwner(userId).endOr();

        List list = query.listPage((int) pageable.getOffset(), pageable.getPageSize());

        return PageUtil.toPage(taskMapper.toDto(list), query.count());
    }

    @Override
    public Map<String, Object> queryDone(TaskQueryCriteria criteria, Pageable pageable) {
        HistoricTaskInstanceQuery query = query(criteria);
        String userId = SecurityUtils.getCurrentUserId().toString();
        query.finished().or().taskAssignee(userId).taskOwner(userId).endOr();
        List list = query.listPage((int) pageable.getOffset(), pageable.getPageSize());

        return PageUtil.toPage(taskMapper.toDto(list), query.count());
    }

    /**
     * 
     * @param dto
     * @param task
     * @param userId
     */
    private void fillPermissionInformation(TaskInfoDto dto, TaskInfo task, String userId) {
        verifyProcessInstanceStartUser(dto, task);

        List<HistoricIdentityLink> taskIdentityLinks = historyService.getHistoricIdentityLinksForTask(task.getId());

        verifyCandidateGroups(dto, userId, taskIdentityLinks);

        verifyCandidateUsers(dto, userId, taskIdentityLinks);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void claimTask(TaskDto dto) {
        String taskId = dto.getId();
        String userId = SecurityUtils.getCurrentUserId().toString();
        TaskInfo task = permissionService.validateReadPermissionOnTask(taskId, userId, false, false);
        if (task.getAssignee() != null && task.getAssignee().length() > 0) {
            throw new FlowableNoPermissionException("User does not have permission");
        }
        addComment(taskId, task.getProcessInstanceId(), userId, CommentTypeEnum.RL, dto.getMessage());
        taskService.claim(taskId, userId);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void unclaimTask(TaskDto dto) {
        String taskId = dto.getId();
        String userId = SecurityUtils.getCurrentUserId().toString();
        TaskInfo task = findTaskById(taskId);
        if (!userId.equals(task.getAssignee())) {
            throw new FlowableNoPermissionException("User does not have permission");
        }
        if (FlowableConstant.INITIATOR.equals(task.getTaskDefinitionKey())) {
            throw new FlowableNoPermissionException("Initiator cannot unclaim the task");
        }
        this.addComment(taskId, task.getProcessInstanceId(), userId, CommentTypeEnum.QXRL, dto.getMessage());
        taskService.unclaim(taskId);
        // 判断是否是协办取消认领
        if (permissionService.isTaskPending((Task) task)) {
            taskService.resolveTask(taskId, null);
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void completeTask(TaskDto dto) {
        String taskId = dto.getId();
        String userId = SecurityUtils.getCurrentUserId().toString();
        Task task = findTaskById(taskId);
        if (!permissionService.isTaskOwnerOrAssignee(userId, task)) {
            if (StringUtils.isEmpty(task.getScopeType())
                    && !permissionService.validateIfUserIsInitiatorAndCanCompleteTask(userId, task)) {
                throw new FlowableNoPermissionException("User does not have permission");
            }
        }

        Map<String, Object> completeVariables = null;
        if (dto.getValues() != null && !dto.getValues().isEmpty()) {
            completeVariables = dto.getValues();
            // 允许任务表单修改流程表单场景 begin
            // 与前端约定：流程表单变量名为
            // processInstanceFormData，且只有流程表单startFormKey=taskFormKey时才允许修改该变量的值，防止恶意节点修改流程表单内容
            if (completeVariables.containsKey(FlowableConstant.PROCESS_INSTANCE_FORM_DATA)) {
                String startFormKey = formService.getStartFormKey(task.getProcessDefinitionId());
                String taskFormKey = formService.getTaskFormKey(task.getProcessDefinitionId(),
                        task.getTaskDefinitionKey());
                boolean modifyProcessInstanceFormData = StringUtils.isNotEmpty(startFormKey)
                        && StringUtils.isNotEmpty(taskFormKey) && startFormKey.equals(taskFormKey);
                if (!modifyProcessInstanceFormData) {
                    throw new FlowableNoPermissionException("User does not have permission");
                }
            }
            // 允许任务表单修改流程表单场景 end
        }
        this.addComment(taskId, task.getProcessInstanceId(), userId,
                FlowableConstant.INITIATOR.equals(task.getTaskDefinitionKey()) ? CommentTypeEnum.CXTJ
                        : CommentTypeEnum.WC,
                dto.getMessage());

        if (task.getAssignee() == null || !task.getAssignee().equals(userId)) {
            taskService.setAssignee(taskId, userId);
        }
        // 判断是否是协办完成还是正常流转
        if (permissionService.isTaskPending(task)) {
            taskService.resolveTask(taskId, completeVariables);
            // 如果当前执行人是任务所有人，直接完成任务
            if (userId.equals(task.getOwner())) {
                taskService.complete(taskId, completeVariables);
            }
        } else {
            taskService.complete(taskId, completeVariables);
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void assignTask(TaskDto dto) {
        String taskId = dto.getId();
        String assignee = dto.getUserId();
        String userId = SecurityUtils.getCurrentUserId().toString();

        Task task = permissionService.validateAssignPermissionOnTask(taskId, userId, assignee);
        this.addComment(taskId, task.getProcessInstanceId(), userId, CommentTypeEnum.ZB, dto.getMessage());
        taskService.setAssignee(task.getId(), assignee);
        // 暂时转办人员不作为参与者
        // String oldAssignee = task.getAssignee();
        // // If the old assignee user wasn't part of the involved users yet, make it so
        // addIdentiyLinkForUser(task, oldAssignee, IdentityLinkType.PARTICIPANT);
        // // If the current user wasn't part of the involved users yet, make it so
        // addIdentiyLinkForUser(task, userId, IdentityLinkType.PARTICIPANT);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void delegateTask(TaskDto dto) {
        String taskId = dto.getId();
        String delegater = dto.getUserId();
        String userId = SecurityUtils.getCurrentUserId().toString();
        Task task = permissionService.validateDelegatePermissionOnTask(taskId, userId, delegater);
        this.addComment(taskId, task.getProcessInstanceId(), userId, CommentTypeEnum.WP, dto.getMessage());
        taskService.delegateTask(task.getId(), delegater);
        // 暂时委派人员不作为参与者
        // String oldAssignee = task.getAssignee();
        // // If the old assignee user wasn't part of the involved users yet, make it so
        // addIdentiyLinkForUser(task, oldAssignee, IdentityLinkType.PARTICIPANT);
        // // If the current user wasn't part of the involved users yet, make it so
        // addIdentiyLinkForUser(task, userId, IdentityLinkType.PARTICIPANT);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Map<String, Object> executeTask(String taskId) {
        String userId = SecurityUtils.getCurrentUserId().toString();
        Task task = permissionService.validateReadPermissionOnTask(taskId, userId, true, true);
        String startFormKey = formService.getStartFormKey(task.getProcessDefinitionId());
        String taskFormKey = formService.getTaskFormKey(task.getProcessDefinitionId(), task.getTaskDefinitionKey());
        Object renderedStartForm = formService.getRenderedStartForm(task.getProcessDefinitionId());
        Object renderedTaskForm = formService.getRenderedTaskForm(taskId);
        Map<String, Object> variables = runtimeService.getVariables(task.getProcessInstanceId());
        ProcessInstance processInstance = runtimeService.createProcessInstanceQuery()
                .processInstanceId(task.getProcessInstanceId()).singleResult();

        Map<String, Object> map = new HashMap<String, Object>(7);
        map.put("startUserId", processInstance.getStartUserId());
        map.put("startFormKey", startFormKey);
        map.put("taskFormKey", taskFormKey);
        map.put("renderedStartForm", renderedStartForm);
        map.put("renderedTaskForm", renderedTaskForm);
        map.put("variables", variables);
        map.put("showBusinessKey", isShowBusinessKey(task.getProcessDefinitionId()));
        map.put("businessKey", processInstance.getBusinessKey());
        // 当前任务是发起者
        if (FlowableConstant.INITIATOR.equals(task.getTaskDefinitionKey())) {
            map.put("isInitiator", true);
        }

        return map;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public List<FlowNodeDto> getBackNodes(String taskId) {
        String userId = SecurityUtils.getCurrentUserId().toString();
        TaskEntity taskEntity = (TaskEntity) permissionService.validateExcutePermissionOnTask(taskId, userId);
        String processInstanceId = taskEntity.getProcessInstanceId();
        String currActId = taskEntity.getTaskDefinitionKey();
        String processDefinitionId = taskEntity.getProcessDefinitionId();
        Process process = repositoryService.getBpmnModel(processDefinitionId).getMainProcess();
        FlowNode currentFlowElement = (FlowNode) process.getFlowElement(currActId, true);
        List<ActivityInstance> activitys = runtimeService.createActivityInstanceQuery()
                .processInstanceId(processInstanceId).finished().orderByActivityInstanceStartTime().asc().list();
        List<String> activityIds = activitys.stream()
                .filter(activity -> activity.getActivityType().equals(BpmnXMLConstants.ELEMENT_TASK_USER))
                .filter(activity -> !activity.getActivityId().equals(currActId)).map(ActivityInstance::getActivityId)
                .distinct().collect(Collectors.toList());

        List<FlowNodeDto> retList = new ArrayList<>();
        for (String activityId : activityIds) {
            FlowNode toBackFlowElement = (FlowNode) process.getFlowElement(activityId, true);
            if (FlowableUtils.isReachable(process, toBackFlowElement, currentFlowElement)) {
                FlowNodeDto dto = new FlowNodeDto();
                dto.setNodeId(activityId);
                dto.setNodeName(toBackFlowElement.getName());
                retList.add(dto);
            }
        }
        return retList;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void backTask(TaskDto dto) {
        String taskId = dto.getId();
        String userId = SecurityUtils.getCurrentUserId().toString();
        Task task = permissionService.validateExcutePermissionOnTask(taskId, userId);
        String backMessage = "退回到" + dto.getActivityName() + "。";
        this.addComment(taskId, task.getProcessInstanceId(), userId, CommentTypeEnum.TH,
                backMessage + dto.getMessage());
        managementService.executeCommand(new BackUserTaskCmd(runtimeService, dto.getId(), dto.getActivityId()));
    }

    @Override
    public void stopTask(TaskDto dto) {
        String taskId = dto.getId();
        String userId = SecurityUtils.getCurrentUserId().toString();
        ProcessInstance processInstance = permissionService.validateStopProcessInstancePermissionOnTask(taskId, userId);
        BpmnModel bpmnModel = repositoryService.getBpmnModel(processInstance.getProcessDefinitionId());
        if (bpmnModel != null) {
            Process process = bpmnModel.getMainProcess();
            List<EndEvent> endNodes = process.findFlowElementsOfType(EndEvent.class, false);
            if (endNodes != null && endNodes.size() > 0) {
                this.addComment(taskId, processInstance.getProcessInstanceId(), userId, CommentTypeEnum.ZZ,
                        dto.getMessage());
                String endId = endNodes.get(0).getId();
                List<Execution> executions = runtimeService.createExecutionQuery()
                        .parentId(processInstance.getProcessInstanceId()).list();
                List<String> executionIds = new ArrayList<>();
                executions.forEach(execution -> executionIds.add(execution.getId()));
                runtimeService.createChangeActivityStateBuilder().moveExecutionsToSingleActivityId(executionIds, endId)
                        .changeState();
            }
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void addComment(String taskId, String processInstanceId, String userId, CommentTypeEnum type,
            String message) {
        Authentication.setAuthenticatedUserId(userId);
        type = (type == null) ? CommentTypeEnum.SP : type;
        message = (message == null || message.length() == 0) ? type.getName() : message;
        taskService.addComment(taskId, processInstanceId, type.toString(), message);
    }

    @Override
    public Task findTaskById(String taskId) {
        Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
        if (task == null) {
            throw new FlowableObjectNotFoundException("Task with id: " + taskId + " does not exist");
        }
        return task;
    }

    /**
     * 验证实例开始用户
     * 
     * @param dto
     * @param task
     */
    private void verifyProcessInstanceStartUser(TaskInfoDto dto, TaskInfo task) {
        if (task.getProcessInstanceId() != null) {
            HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery()
                    .processInstanceId(task.getProcessInstanceId()).singleResult();
            if (historicProcessInstance != null && StringUtils.isNotEmpty(historicProcessInstance.getStartUserId())) {
                dto.setProcessInstanceStartUserId(historicProcessInstance.getStartUserId());
                BpmnModel bpmnModel = repositoryService.getBpmnModel(task.getProcessDefinitionId());
                FlowElement flowElement = bpmnModel.getFlowElement(task.getTaskDefinitionKey());
                if (flowElement instanceof UserTask) {
                    UserTask userTask = (UserTask) flowElement;
                    List<ExtensionElement> extensionElements = userTask.getExtensionElements()
                            .get("initiator-can" + "-complete");
                    if (CollectionUtils.isNotEmpty(extensionElements)) {
                        String value = extensionElements.get(0).getElementText();
                        if (StringUtils.isNotEmpty(value)) {
                            dto.setInitiatorCanCompleteTask(value);
                        }
                    }
                }
            }
        }
    }

    /**
     * 验证候选人
     * 
     * @param dto
     * @param userId
     * @param taskIdentityLinks
     */
    private void verifyCandidateGroups(TaskInfoDto dto, String userId, List<HistoricIdentityLink> taskIdentityLinks) {
        List<Group> userGroups = identityService.createGroupQuery().groupMember(userId).list();
        dto.setMemberOfCandidateGroup(
                String.valueOf(userGroupsMatchTaskCandidateGroups(userGroups, taskIdentityLinks)));
    }

    /**
     * 匹配任务候选人
     * 
     * @param userGroups
     * @param taskIdentityLinks
     * @return
     */
    private boolean userGroupsMatchTaskCandidateGroups(List<Group> userGroups,
            List<HistoricIdentityLink> taskIdentityLinks) {
        for (Group group : userGroups) {
            for (HistoricIdentityLink identityLink : taskIdentityLinks) {
                if (identityLink.getGroupId() != null && identityLink.getType().equals(IdentityLinkType.CANDIDATE)
                        && group.getId().equals(identityLink.getGroupId())) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * 验证候选人
     * 
     * @param dto
     * @param userId
     * @param taskIdentityLinks
     */
    private void verifyCandidateUsers(TaskInfoDto dto, String userId, List<HistoricIdentityLink> taskIdentityLinks) {
        dto.setMemberOfCandidateUsers(String.valueOf(currentUserMatchesTaskCandidateUsers(userId, taskIdentityLinks)));
    }

    /**
     * 匹配候选人
     * 
     * @param userId
     * @param taskIdentityLinks
     * @return
     */
    private boolean currentUserMatchesTaskCandidateUsers(String userId, List<HistoricIdentityLink> taskIdentityLinks) {
        for (HistoricIdentityLink identityLink : taskIdentityLinks) {
            if (identityLink.getUserId() != null && identityLink.getType().equals(IdentityLinkType.CANDIDATE)
                    && identityLink.getUserId().equals(userId)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 办理人
     * 
     * @param task
     * @param dto
     */
    private void populateAssignee(TaskInfo task, TaskInfoDto dto) {
        if (task.getAssignee() != null) {
            dto.setAssignee(task.getAssignee());
        }
    }

    /**
     * 获取授权用户
     * 
     * @param taskId
     * @return
     */
    private List<String> getInvolvedUsers(String taskId) {
        List<HistoricIdentityLink> taskIdentityLinks = historyService.getHistoricIdentityLinksForTask(taskId);
        List<String> retList = new ArrayList<>(taskIdentityLinks.size());
        for (HistoricIdentityLink identityLink : taskIdentityLinks) {
            // Only include users and non-assignee links
            if (identityLink.getUserId() != null && !IdentityLinkType.ASSIGNEE.equals(identityLink.getType())) {
                retList.add(identityLink.getUserId());
            }
        }
        return retList;
    }

    /**
     * 
     * @param userId
     * @return
     */
    private String getUserName(String userId) {
        if (StringUtils.isEmpty(userId)) {
            return null;
        }
        User user = identityService.createUserQuery().userId(userId).singleResult();
        if (user != null) {
            return user.getFirstName();
        }
        return null;
    }

    protected boolean isShowBusinessKey(String processDefinitionId) {
        List<ValuedDataObject> dataObjects = repositoryService.getBpmnModel(processDefinitionId).getMainProcess()
                .getDataObjects();
        if (dataObjects != null && dataObjects.size() > 0) {
            for (ValuedDataObject valuedDataObject : dataObjects) {
                if ("showBusinessKey".equals(valuedDataObject.getId())) {
                    if (valuedDataObject.getValue() instanceof String) {
                        return Boolean.valueOf((String) valuedDataObject.getValue());
                    } else if (valuedDataObject.getValue() instanceof Boolean) {
                        return (Boolean) valuedDataObject.getValue();
                    } else {
                        return false;
                    }
                }
            }
        }
        return false;
    }
}
